package cn.tedu.charging.order.service;

import cn.tedu.charging.common.pojo.vo.ChargingProcessVO;
import cn.tedu.charging.order.constant.Constant;
import cn.tedu.charging.order.dao.repository.OrderRepository;
import cn.tedu.charging.order.feign.CostClient;
import cn.tedu.charging.order.feign.DeviceClient;
import cn.tedu.charging.order.feign.UserClient;
import cn.tedu.charging.order.job.DeviceCheckJob;
import cn.tedu.charging.order.mqtt.MqttProducer;
import cn.tedu.charging.order.pojo.param.ChargingResultParam;
import cn.tedu.charging.order.pojo.dto.CarInfoDto;
import cn.tedu.charging.order.pojo.dto.ChargingDto;
import cn.tedu.charging.order.pojo.dto.StationInfoDto;
import cn.tedu.charging.order.pojo.param.OrderAddParam;
import cn.tedu.charging.order.pojo.po.ChargingBillSuccessPO;
import cn.tedu.charging.order.pojo.po.OrderMQPO;
import cn.tedu.charging.order.pojo.vo.OrderDetailVO;
import cn.tedu.charging.order.rabbitmq.RabbitMQOrderProducer;
import cn.tedu.charging.common.web.JsonResult;
import cn.tedu.charging.common.pojo.vo.StationInfoVO;
import cn.tedu.charging.common.pojo.vo.UserInfoVO;
import cn.tedu.charging.common.pojo.param.GunStatusUpdateParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.Random;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService{
    /**
     * 访问用户服务的feign客户端
     */
    @Autowired
    private UserClient userClient;
    /**
     * 访问设备服务的feign客户端
     */
    @Autowired
    private DeviceClient deviceClient;
    /**
     * mqtt 消息发送工具
     */
    @Autowired
    private MqttProducer mqttProducer;
    /**
     * rabbit-mq 消息发送工具
     */
    @Autowired
    private RabbitMQOrderProducer rabbitMQOrderProducer;
    /**
     * 数据操作层
     */
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private CostClient costClient;

    /**
     *
     * @param orderAddParam
     * @return
     *
     * 1 扫码
     * 2 判断余额 余额够 继续
     * 3 创建订单
     * 4 给设备下发指令 开始充电  应该选择哪种通信方式 该怎么告诉设备开始充电
     * 同步
     * 优点
     * 站在发送者的角度  比较简单 好理解
     * 10:01
     *  Boolean  success  = startCharging() //发生充电指令
     *  if (success) { //判断是否开始充电
     *         10:05
     *         insertOrderData();//创建订单 数据库有记录
     *         return orderNo;
     *  }
     * 缺点
     *
     * 站在接收者的角度
     *      * 设备有多种情况
     *      *  1 没坏 收到了,但是因为网络原因,没有很快响应 告诉接受者
     *      *  2 坏了 导致设备收不到,  没有很快响应 告诉接受者
     *      *
     *      * 发送者不知道设备的状态 是能充还是不能充 等待 页面在转
     *
     * 异步
     *    startCharging();//发生充电指令 发送者
     *    insertOrderData();//创建订单 数据库有记录
     *    return orderNo;
     * 优点
     *
     * 缺点
     * 上面的代码没有实时得到充电桩的状态 能充还是不能充
     *  得有方案来接收 充电桩的状态
     *
     *  接受者
     *  发送完充电指令后,继续执行后面的业务逻辑,当充电桩有响应
     *   正常 开始充电
     *   异常 设备故障  无响应
     *  做后续的处理
     *    开始充电 计费
     *    设备故障  无响应 订单取消,通知用户
     *
     *  和设备通信  MQTT 物联网技术  类似与消息队列  异步和解耦
     *
     *  5 创建订单
     *  6 创建订单编号 唯一  能不能用数据库的自增主键?  1 2 3
     *      不用数据库主键 分库
     *      雪花算法  自己了解 分布式的id生成器
     *      UUID 没有顺序,mysql是建议有顺序 , uuid + 时间戳
     *      自定义   城市编号 + 随机数 + 时间
     *
     *
     *
     *  7 返回订单编号
     *
     *
     *
     */

    @Override
    public String createOrder(OrderAddParam orderAddParam) throws Exception{
        //todo 订单创建的流程
        //1.订单编号创建
        String orderNo = getOrderNo();
        log.debug("生成订单号：{}",orderNo);
        log.debug("生成设备自检任务:{}",orderNo);
        DeviceCheckJob job = new DeviceCheckJob(orderNo,orderAddParam.getGunId());
        log.debug("生成设备自检任务成功:{},出参:{}",orderNo,job);
        //2.通过枪id获取运营商信息和充电站信息
        log.debug("获取运营商和场站信息-入参:{}",orderAddParam.getGunId());
        StationInfoDto stationInfoDto =getStationInfo(orderAddParam.getGunId());
        log.debug("获取运营商和场站信息-入参:{},出参:{}",orderAddParam.getGunId(),stationInfoDto);
        //3.通过用户id获取用户车辆信息
        log.debug("获取用户车辆信息-入参:{}",orderAddParam.getUserId());
        CarInfoDto carInfoDto = getCarInfo(orderAddParam.getUserId());
        log.debug("获取用户车辆信息-入参:{},出参:{}",orderAddParam.getUserId(),carInfoDto);
        //4.通过枪的id修改枪的状态为已使用
        //2表示已使用 就传2
        log.debug("设置充电枪状态为已经使用-入参:枪id-{},状态-{}",orderAddParam.getGunId(),Constant.GUN_STATS_BUSY);
        Boolean success = updateGunStatus(orderAddParam.getGunId(), Constant.GUN_STATS_BUSY);// 2 魔数
        log.debug("设置充电枪状态为已经使用-入参:枪id-{},状态-{},出参:{}",orderAddParam.getGunId(),Constant.GUN_STATS_BUSY,success);
        // 1表示空闲
        //updateGunStatus(orderAddParam.getGunId(),1);

        //updateGunStatusOff(orderAddParam.getGunId());
        //updateGunStatusOn(orderAddParam.getGunId());
        if (!success) {
            log.warn("修改枪状态失败,枪id:{},状态:{}",orderAddParam.getGunId(),Constant.GUN_STATS_BUSY);
        }
        //给充电桩发送充电指令
        startCharging(orderNo,orderAddParam.getPileId(),orderAddParam.getGunId());
        // 正常情况 设备会定时和订单服务同步数据,温度,电压,用电度数
        // 异常情况 设备接收到开始充电指令后,开始充电,充了一会,设备故障,不会再和订单服务同步数据
        // 订单需要处理这种数据
        // 1 延迟消息  rabbit-mq
        // 2 定时任务
        // 总结 场景/问题/解决

        //发送订单
        sendOrder(orderNo);
        return orderNo;
    }
    /**
     * 发送延迟消息
     * @param orderNo
     */
    private void sendOrder(String orderNo) {
        OrderMQPO orderMQPO = new OrderMQPO();
        orderMQPO.setOrderNo(orderNo);
        rabbitMQOrderProducer.sendOrder(orderMQPO);
    }


    /**
     * 保存成功订单
     */
    @Override
    public  void saveSuccessOrder(ChargingResultParam chargingResultParam) {
        ChargingBillSuccessPO successPO = new ChargingBillSuccessPO();
        successPO.setBillId(chargingResultParam.getOrderNo());
        orderRepository.saveSuccessOrder(successPO);
    }

    @Override
    public void saveFailOrder() {

    }

    @Override
    public OrderDetailVO normalStop(String orderNo) {
        ChargingBillSuccessPO chargingBillSuccessPO = getOrderInfoByOrderNo(orderNo);
        if (chargingBillSuccessPO != null){
            //获取订单花费
            ChargingProcessVO chargingProcessResult = getOrderCostByOrderNo(orderNo);
            if (chargingProcessResult != null){
                //todo 给充电桩发送停止充电指令
                //更新订单状态
                updateOrderStatus2NormalStop(orderNo);
                //更新设备状态
                updateGunStatus(chargingBillSuccessPO.getGunId(),Constant.GUN_STATS_FREE);
                //获取用户最新的余额
                BigDecimal userBalance =getUserBalance(chargingBillSuccessPO.getUserId());
                //扣钱  用户最新的余额 -  订单花费
                BigDecimal currentBalance = userBalance.subtract(chargingProcessResult.getTotalCost());
                //更新用户余额 事务的问题 分布式事务  todo
                updateUserBalance(chargingBillSuccessPO.getUserId(),currentBalance);
                //返回扣除当前订单话费的余额
                OrderDetailVO orderDetailVO = buildOrderDetailVO(orderNo, currentBalance, chargingProcessResult);
                return orderDetailVO;
            }else {
                //自定义抛出异常 订单获取价格信息失败 todo
            }
        }else {
            //自定义抛出异常 订单不存在 todo
        }
        OrderDetailVO orderDetailVO = new OrderDetailVO();
        orderDetailVO.setOrderNo(orderNo);
        return orderDetailVO;
    }

    /**
     * 更新用户余额
     * @param userId
     * @param currentBalance
     */
    private void updateUserBalance(Integer userId, BigDecimal currentBalance) {
        log.debug("更新用户余额,用户id:{},余额:{}",userId,currentBalance);

    }

    /**
     * 构建正常停止充电返回结果
     * @param orderNo
     * @param currentBalance
     * @param chargingProcessResult
     * @return
     */
    private OrderDetailVO buildOrderDetailVO(String orderNo, BigDecimal currentBalance, ChargingProcessVO chargingProcessResult) {
        OrderDetailVO orderDetailVO = new OrderDetailVO();
        orderDetailVO.setOrderNo(orderNo);
        orderDetailVO.setBalance(currentBalance);
        orderDetailVO.setTotalCost(chargingProcessResult.getTotalCost());
        orderDetailVO.setHours(chargingProcessResult.getHours());
        orderDetailVO.setMinutes(chargingProcessResult.getMinutes());
        orderDetailVO.setSeconds(chargingProcessResult.getSeconds());
        return orderDetailVO;
    }

    private BigDecimal getUserBalance(Integer userId) {
        log.debug("调用用户服务获取用户余额:{}",userId);
        JsonResult<BigDecimal> userBalance = userClient.getUserBalance(userId);
        log.debug("调用用户服务获取用户余额:入参{},出参{}",userId,userBalance);
        if (userBalance != null){
            return userBalance.getData();
        }
        return new BigDecimal(0);
    }
    private void updateOrderStatus2NormalStop(String orderNo) {
        orderRepository.updateOrderStatusFromProgress2NormalEnd(orderNo);
    }

    /**
     * 通过订单号获取当前订单花费
     * @param orderNo
     * @return
     * @throws Exception
     */
    private ChargingProcessVO getOrderCostByOrderNo(String orderNo) {
        JsonResult<ChargingProcessVO> costByOrderNo = costClient.getCostByOrderNo(orderNo);
        if (costByOrderNo !=null){
            return costByOrderNo.getData();
        }
        return null;
    }

    /**
     * 通过订单号查询订单信息
     * @param orderNo
     * @return
     */
    private ChargingBillSuccessPO getOrderInfoByOrderNo(String orderNo) {
        return orderRepository.getSuccess(orderNo);

    }

    /**
     * 异步下发充电指令
     * @param orderNo 订单编号
     * @param pileId 充电桩id
     * @param gunId  枪id
     */


    private void startCharging(String orderNo,Integer pileId,Integer gunId) {
        log.debug("发送指令:订单号-{},桩id-{},枪id-{}",orderNo,pileId,gunId);
        ChargingDto chargingDto = new ChargingDto();
        chargingDto.setGunId(gunId);
        chargingDto.setPileId(pileId);
        chargingDto.setMsg(Constant.START_CHARGING);//开始充电的指令
        chargingDto.setOrderNo(orderNo);
        mqttProducer.send(chargingDto);
        log.debug("发送充电指令-入参:{}",chargingDto);
    }

    /**
     * 更新枪的状态
     * @param gunId
     * @param status
     * @return
     */
    private Boolean updateGunStatus(Integer gunId, Integer status) {
        GunStatusUpdateParam param = new GunStatusUpdateParam();
        param.setId(gunId);
        param.setStatus(status);
        JsonResult<Boolean> jsonResult = deviceClient.updateGunStatus(param);
        if (jsonResult != null){
            return jsonResult.getData();
        }
        return false;
    }


    /**
     * 通过feign的方式调用用户服务
     * @param userId
     * @return
     */
    private CarInfoDto getCarInfoByFeign(Long userId){
        JsonResult<UserInfoVO> userInfo = userClient.getUserInfo(userId);
        if (userInfo!=null){
            UserInfoVO data = userInfo.getData();
            if (data != null){
                CarInfoDto carInfoDto = new CarInfoDto();
                BeanUtils.copyProperties(data,carInfoDto);
                return carInfoDto;
            }
        }
       return null;
    }

    @Autowired
    private RestTemplate restTemplate;

    public String chooseUrl() {
        String url = "http://localhost:8083/user/info/{1}";
        String url1 = "http://localhost:8084/user/info/{1}";
        String url2 = "http://localhost:8085/user/info/{1}";
        String[] servers = new String[3];
        servers[0] = url;
        servers[1] = url1;
        servers[2] = url2;
        Random random = new Random();
        int r = random.nextInt(servers.length);
        String server = servers[r];
        return server;
    }

    /**
     * 通过用户id获取用户绑定的车辆信息
     * 先假定一个用户只能绑定一辆车 充电的时候就是用户绑定的那辆唯一的车
     * 如果后续功能迭代 充电的时候由用户选择给哪辆车充电, 车的id可以通过入参的方式传入
     * @param userId
     * @return
     */
    private CarInfoDto getCarInfo(Long userId) {
        return getCarInfoByFeign(userId);
    }

    /**
     * 通过RestTemplate 来调用用户服务
     * @param userId
     *
     * @return
     */
    private CarInfoDto getCarInfoByRestTemplate(Long userId){
        //String url = "http://localhost:8083/user/info/{1}";
        String url = chooseUrl();
        log.debug("通过restTemplate调用用户接口获取用户信息-入参url:{}",url);
        log.debug("通过restTemplate调用用户接口获取用户信息-入参:{}",userId);
        //不使用 ParameterizedTypeReference的情况下，直接使用 Jackson 反序列化会导致泛型类型信息的丢失，无法正确反序列化成期望的泛型类型对象。
        // 这会导致你在处理响应数据时遇到困难，因为你必须手动将 LinkedHashMap 转换为具体的类型。这不仅增加了代码的复杂性，而且容易出错。
        // 因此，使用 ParameterizedTypeReference 或 TypeReference 是确保正确处理和反序列化泛型类型响应数据的推荐方式。
        ParameterizedTypeReference<JsonResult<UserInfoVO>> responseBody =
                new ParameterizedTypeReference<JsonResult<UserInfoVO>>() {};
        ResponseEntity<JsonResult<UserInfoVO>> entity =
                restTemplate.exchange(url, HttpMethod.GET, null, responseBody, userId);
        log.debug("通过restTemplate调用用户接口获取用户信息-入参:{},出参:{}",userId,entity);
        if (entity!=null){
            HttpStatus statusCode = entity.getStatusCode();
            if (HttpStatus.OK == statusCode){
                JsonResult<UserInfoVO> body = entity.getBody();
                UserInfoVO data = body.getData();
                log.debug("通过restTemplate调用用户接口获取用户信息-入参:{},body:{}",userId,body);
                CarInfoDto carInfoDto = new CarInfoDto();
                BeanUtils.copyProperties(data,carInfoDto);
                return carInfoDto;
            }
        }
  /*            //body 的内容 JsonResult(code=2000, msg=null, data={carId=11})
                //获取 data={carId=11};
                //强转 把object强制转换为 CarInfoDto
                Object result = body.getData();
                //result 看起来是个{carId=11} json,其实他是个map
                //json的工具的使用  spring-boot 默认使用的是jackson
                ObjectMapper objectMapper = new ObjectMapper();
                //obj 怎么转换为string
                //String s = result.toString();
                //String str = result +"";
                String str = String.valueOf(result); //return (obj == null) ? "null" : obj.toString();
                try {
                    CarInfoDto carInfoDto = objectMapper.readValue(str, CarInfoDto.class);
                } catch (JsonProcessingException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        CarInfoDto carInfoDto = new CarInfoDto();
        carInfoDto.setCarId(15);    */
        return null;
    }


    /**
     * 通过枪id获取运营商信息和充电站信息
     * @param gunId
     * @return StationInfoDto
     */
    private StationInfoDto getStationInfo(Integer gunId) {
        JsonResult<StationInfoVO> stationInfo = deviceClient.getStationInfo(gunId);
        if (stationInfo!=null){
            StationInfoVO data = stationInfo.getData();
            if (data != null){
                StationInfoDto stationInfoDto = new StationInfoDto();
                BeanUtils.copyProperties(data,stationInfoDto);
                return stationInfoDto;
            }
        }
        return null;
    }

    /**
     * 生成订单号
     *  固定开始的 10000 + _ + 随机数 + _ + 时间戳
     * @return
     */
    private String getOrderNo() {
        //换一种算法 雪花
        String start = "10000";
        long now = System.currentTimeMillis();
        Random random = new Random();
        int r = random.nextInt(100);
        String orderNo =  start + "_" + "_" + r +  now;
        return orderNo;
    }

}
